#include "goto-checker.hpp"

#include <clang/ASTMatchers/ASTMatchFinder.h>
#include <clang/AST/ASTContext.h>
#include <stack>

using namespace clang::ast_matchers;

void GotoChecker::run(const MatchFinder::MatchResult &Result) {
	const clang::FunctionDecl* func =
		Result.Nodes.getNodeAs<clang::FunctionDecl>("func");
	if (func) {
		in_error_section = false;
		while (!gotos.empty()) gotos.pop();
		seen_jumps.clear();
		//seen_ints->clear();
	}

	const clang::GotoStmt* g =
		Result.Nodes.getNodeAs<clang::GotoStmt>("goto");
	if (g) {
		clang::LabelDecl* label = g->getLabel();
		if (in_error_section) {
			clang::DiagnosticsEngine& d =
				Result.Context->getDiagnostics();
			unsigned int id = d.getCustomDiagID(
				clang::DiagnosticsEngine::Warning,
				"Found goto to label %0 inside "
				"error hanling section"
			);
			d.Report(g->getLocStart(), id) << label;
			return;
		}
		if (seen_jumps.find(label->getNameAsString()) != seen_jumps.end()) {
			clang::DiagnosticsEngine& d =
				Result.Context->getDiagnostics();
			unsigned int id = d.getCustomDiagID(
				clang::DiagnosticsEngine::Warning,
				"Multiple jumps to label %0."
			);
			const clang::GotoStmt* first_jump = seen_jumps.find(label->getNameAsString())->second;
			d.Report(g->getLocStart(), id) << label;
			unsigned int note = d.getCustomDiagID(
				clang::DiagnosticsEngine::Note,
				"First jump was here."
			);
			d.Report(first_jump->getLocStart(), note) << label;
			return;
		}
		gotos.push(label);
		seen_jumps[label->getNameAsString()]=g;
	}

	const clang::LabelStmt* label =
		Result.Nodes.getNodeAs<clang::LabelStmt>("label");
	if (label) {
		if (!in_error_section) {
			// TODO: Check somehow that all paths have returned
		}
		in_error_section = true;
		clang::LabelDecl* found = label->getDecl();
		std::string name = found->getNameAsString();
		if (
			name.substr(0, 6) != "error_"
			&& name.substr(0, 8) != "cleanup_"
			&& name.substr(0, 4) != "err_"
			&& name != "exit"
		) {
			clang::DiagnosticsEngine& d =
				Result.Context->getDiagnostics();
			unsigned int id = d.getCustomDiagID(
				clang::DiagnosticsEngine::Warning,
				"Illegal label name: %0"
			);
			d.Report(label->getLocStart(), id) << found;
			return;
		}
		if (gotos.empty()) {
			/* Something's strange here, but we can let normal
			 * compiler warnings handle that. */
			return;
		}
		clang::LabelDecl* expected = gotos.top();

		if (found != expected) {
			clang::DiagnosticsEngine& d =
				Result.Context->getDiagnostics();
			unsigned int id = d.getCustomDiagID(
				clang::DiagnosticsEngine::Warning,
				"Error handling sequence mismatch. "
				"Expected %0, found %1"
			);
			d.Report(label->getLocStart(), id) << expected << found;
		}

		gotos.pop();
	}
}
